package com.istock.monitor.agent.spring;

import java.util.ArrayList;
import java.util.List;

import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.aop.config.MethodLocatingFactoryBean;
import org.springframework.aop.config.SimpleBeanFactoryAwareAspectInstanceFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;

import com.istock.monitor.agent.model.AOPHandler;

public class AutoAOPBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor{

	private String expression;
	public void setExpression(String expression) {
		this.expression = expression;
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(registry);
//		ClassNameAutoProxyCreator.registerAspectJAutoProxyCreatorIfNecessary(registry);
		
		BeanDefinition aspectFactoryDef = createAspectComponentDefinition();
		registry.registerBeanDefinition(CMP_ASPECT_NAME, aspectFactoryDef);
		
		BeanDefinition handlerBean = createAOPHandlerBeanDefinition();
		registry.registerBeanDefinition(CMP_HANDLER_NAME, handlerBean);
		
		BeanDefinition advisorBean = createCMPAdvisorBeanDefinition(CMP_HANDLER_NAME,aspectFactoryDef);
		
		List<BeanDefinition> apsectDefList = new ArrayList<BeanDefinition>();
		apsectDefList.add(advisorBean);
		
		List<BeanReference> referenceList = new ArrayList<BeanReference>();
		referenceList.add(new RuntimeBeanReference(CMP_HANDLER_NAME));
		
		registry.registerBeanDefinition(CMP_ADVISOR_NAME, advisorBean);
	}

	private static final String EXPRESSION = "expression";
	private static final String DECLARATION_ORDER_PROPERTY = "declarationOrder";
	private static final int METHOD_INDEX = 0;
	private static final int POINTCUT_INDEX = 1;
	private static final int ASPECT_INSTANCE_FACTORY_INDEX = 2;
	private static final String ORDER_PROPERTY = "order";
	private static final String ASPECT_NAME_PROPERTY = "aspectName";
	
	private static final String CMP_ASPECT_NAME = "CMP-ASPECT";
	private static final String CMP_HANDLER_NAME = "CMP-HANDLER";
	private static final String CMP_ADVISOR_NAME = "CMP-ADVISOR";
	
	protected AbstractBeanDefinition createPointcutDefinition(String expression) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(MonitorAspectJExpressionPointCut.class);
		beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
		beanDefinition.setSynthetic(true);
		beanDefinition.getPropertyValues().add(EXPRESSION, expression);
		return beanDefinition;
	}
	
	private BeanDefinition createAOPHandlerBeanDefinition() {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(AOPHandler.class);
		return beanDefinition;
	}
	
	private BeanDefinition createAspectComponentDefinition() {

		RootBeanDefinition aspectFactoryDef =
				new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
		aspectFactoryDef.getPropertyValues().add("aspectBeanName", CMP_HANDLER_NAME);
		aspectFactoryDef.setSynthetic(true);
		
		return aspectFactoryDef;
		
	}

	private BeanDefinition createCMPAdvisorBeanDefinition(String handlerName , BeanDefinition aspectFactoryDef) {
		
		RootBeanDefinition adviceDefinition = new RootBeanDefinition(AspectJAroundAdvice.class);
		adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, handlerName);
		adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, Integer.MAX_VALUE);
		
		ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
		
		RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
		methodDefinition.getPropertyValues().add("targetBeanName", handlerName);
//		methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
		methodDefinition.getPropertyValues().add("methodName", "around");
		methodDefinition.setSynthetic(true);
		cav.addIndexedArgumentValue(METHOD_INDEX, methodDefinition);
		
		/*BeanDefinition pointCutBean = createPointcutDefinition("(execution(* com.istock.user..*.*(..)) or execution(* com.istock.union..*.*(..))) && !within(is(EnumType)) && !within(is(FinalType)) && !execution(* com.istock.union.user.util.*.*(..)) "
				+ "&& !execution(* com.istock.union.user.client.*.*(..)) && !execution(* com.istock.union.filter.*.*(..)) ");*/
		BeanDefinition pointCutBean = createPointcutDefinition(expression);
		cav.addIndexedArgumentValue(POINTCUT_INDEX, pointCutBean);
		
		cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);
		
		RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
		advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDefinition);
		advisorDefinition.getPropertyValues().add(ORDER_PROPERTY, Integer.MAX_VALUE-1);
		
		return advisorDefinition;
	}
}
